/* Assignment 1 */
/* James Gregory: 3288801        Huy Nguyen: 3430069*/
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

static unsigned char * counter;
static int volatile done = 0;
int counter_size;

#ifdef DEBUG
void print_local_counter(unsigned char *lcounter, void *threadid) {
	int current_byte = 0;
	char buffer[5*counter_size];
	long tid = (long)threadid;
	sprintf(buffer, "Thread #%ld ", tid);
	while(current_byte < counter_size) {
		sprintf(buffer + strlen(buffer), "[%d] ", *lcounter);
		lcounter++;
		current_byte++;
	}
	printf("%s\n", buffer);
}

void print_counter(unsigned char *lcounter) {
	char buffer[5*counter_size*2];
	int current_byte;
	sprintf(buffer, "Main: ");
	for(current_byte = 0; current_byte < counter_size * 2; current_byte++) {
		sprintf(buffer + strlen(buffer), "[%d] ", counter[current_byte]);
	}
	printf("%s\n", buffer);
}
#endif

void *reader(void *arg) {
	unsigned char lcounter[counter_size];

	while(!done) {
		int index = 0;
		while(index < counter_size) {
			lcounter[counter_size - index - 1] = counter[index];
			if(counter[index] == counter[counter_size * 2 - index - 1]) {
				index++;
			} else {
				memset(lcounter, 255, counter_size - index - 1);
				break;			
			}
		}
#ifdef DEBUG
		print_local_counter(lcounter, arg);
#endif
	}

	pthread_exit(NULL);
}


void increment(unsigned char * lcounter) {
	int current_byte = 0;
	lcounter[current_byte + counter_size]++;
	lcounter[counter_size - 1 - current_byte] = lcounter[current_byte + counter_size];
	while(current_byte < counter_size && lcounter[current_byte + counter_size] == 0) {
		lcounter[++current_byte + counter_size]++;
		lcounter[counter_size - 1 - current_byte] = lcounter[current_byte + counter_size];
	}
	memcpy(counter, lcounter, sizeof(char) * counter_size * 2);
#ifdef DEBUG
	print_counter(lcounter);
#endif
}

void *writer(void *arg) {
	long rounds = (long)arg;
        unsigned char * lcounter = (unsigned char*)malloc(counter_size * 2);
	memset(lcounter, 0, counter_size * 2);
	
	if (rounds > 0) {
		long round_counter;
		for(round_counter=0; round_counter<rounds; round_counter++) increment(lcounter);
		done = -1;
	} else if(rounds == 0) {
		while(1) increment(lcounter);
	}
	pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
	if (argc < 3 || argc > 4) {
		printf("Invalid number of arguments.\n");
		printf("counter <readers> <counter bytes> <rounds>\n");
		exit(0);
	}
	counter_size = atoi(argv[2]);
	int number_of_readers = atoi(argv[1]);
	long rounds = (argc == 4) ? atol(argv[3]) : 0;

	counter = (unsigned char*)malloc(counter_size * 2);
	memset(counter, 0, counter_size * 2);

	pthread_t threads[number_of_readers + 1];
	int result;
	result = pthread_create(&threads[0], NULL, writer, (void *)rounds);
	if(result) exit(-1);
	long reader_id;
	for(reader_id = 1; reader_id <= number_of_readers; reader_id++) {
		result = pthread_create(&threads[reader_id], NULL, reader, (void *)reader_id);
		if(result) exit(-1);
	}

	for(reader_id = 0; reader_id < number_of_readers; reader_id++) {
		pthread_join(threads[reader_id], NULL);
	}
	pthread_exit(NULL);
}
